// (c) Copyright 2013 Stephen Gold <sgold@sonic.net>
// Distributed under the terms of the GNU General Public License

/*
 This file is part of the Bats Game.

 The Bats Game is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by the
 Free Software Foundation, either version 3 of the License, or (at your
 option) any later version.

 The Bats Game is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.

 You should have received a copy of the GNU General Public License
 along with the Bats Game.  If not, see <http://www.gnu.org/licenses/>.
 */
package bats.ui;

import bats.map.Blocks;
import bats.scene.LightingPreset;
import bats.scene.PovMode;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import jme3utilities.ui.Hotkey;
import jme3utilities.ui.Signals;

/**
 * Validate strings which represent actions initiated by the user. Aside from
 * test cases, all methods here should be public and static.
 *
 * @author Stephen Gold <sgold@sonic.net>
 */
public class Actions {
    // *************************************************************************
    // constants

    /**
     * message logger for this class
     */
    final private static Logger logger =
            Logger.getLogger(Actions.class.getName());
    // *************************************************************************
    // fields
    /**
     * set of valid non-signal action names
     */
    final private static Set<String> validNames = new TreeSet<>();
    /**
     * set of valid non-signal action prefixes
     */
    final private static Set<String> validPrefixes = new TreeSet<>();
    /**
     * tracker for command signals
     */
    private static Signals signals = null;
    // *************************************************************************
    // constructors

    /**
     * A private constructor to inhibit instantiation of this class.
     */
    private Actions() {
    }
    // *************************************************************************
    // new methods exposed

    /**
     * Add all actions generated by a specified menu to the list of valid
     * strings.
     *
     * @param prefix the action prefix of the menu (not null, not empty)
     * @param items the items in the menu (not null)
     */
    public static void addMenu(String prefix, String[] items) {
        assert prefix != null;
        assert !prefix.isEmpty();
        assert items != null;

        if (!validNames.contains(prefix)) {
            addName(prefix);
        }
        for (String item : items) {
            String name = String.format("%s %s", prefix, item);
            addName(name);
        }
    }

    /**
     * Add a specified non-signal action name to the set of valid names.
     *
     * @param name which name to add (not null)
     */
    public static void addName(String name) {
        assert name != null;
        assert !name.isEmpty();

        boolean success = validNames.add(name);
        assert success : name;
    }

    /**
     * Add a specified action prefix to the set of valid prefixes.
     *
     * @param prefix the action prefix (not null, not empty)
     */
    public static void addPrefix(String prefix) {
        assert prefix != null;
        assert !prefix.isEmpty();

        boolean success = validPrefixes.add(prefix);
        assert success : prefix;
    }

    /**
     * Access the set of valid non-signal action names.
     *
     * @return read-only, pre-existing instance
     */
    public static Set<String> getNames() {
        return Collections.unmodifiableSet(validNames);
    }

    /**
     * Test whether an action string starts with a valid prefix.
     *
     * @param string which action string to test (not null)
     */
    public static boolean hasValidPrefix(String string) {
        assert string != null;

        for (String prefix : validPrefixes) {
            if (string.startsWith(prefix)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Initialize the sets of valid names and prefixes.
     */
    public static void initialize(Signals sigs) {
        assert sigs != null;

        signals = sigs;

        addName("add fire");
        addMenu("add block", HeadsUpDisplay.addBlockItems);
        addMenu("add block tower", Blocks.addBlockTowerItems);
        addMenu("add prop", HeadsUpDisplay.addPropItems);
        addMenu("add unit", HeadsUpDisplay.getAddUnitItems());
        addName("aim");
        addName("close");
        addName("delete flames");
        addName("delete picked");
        addName("duplicate picked");
        addName("go bind");
        addName("go build");
        addName("go pose");
        addName("go sim");
        addName("impair");
        addMenu("load map", HeadsUpDisplay.loadMapItems);
        addName("lock");
        addName("pick");
        addName("pick all");
        addName("pick invert");
        addName("pick none");
        addName("print map");
        addName("print pickables");
        addName("print root");
        addName("print scene");
        addName("rotate left");
        addName("rotate right");
        addName("save map");
        addPrefix("select animation");
        addPrefix("select bone");
        addMenu("select lighting", LightingPreset.selectLightingItems);
        addMenu("select model", PoseHud.getSelectModelItems());
        addMenu("select pov", PovMode.selectPovItemsAll);
        addName("step back");
        addName("step down");
        addName("step forward");
        addName("step left");
        addName("step right");
        addName("step up");
        addName("step viewBottom");
        addName("step viewLeft");
        addName("step viewRight");
        addName("step viewTop");
        addName("toggle 3DCursor");
        addName("toggle aggressive");
        addName("toggle axes");
        addName("toggle ground");
        addName("toggle hud");
        addName("toggle pause");
        addName("toggle physics");
        addName("toggle shadows");
        addName("toggle skeleton");
        addName("toggle skeleton all");
        addName("toggle target all");
        addName("toggle terrain");
        addName("toggle viewpoint all");
        addName("unlock");
        addName("view horizontal");
        addName("view overhead");
    }

    /**
     * Validate an action name.
     *
     * @param name which name to validate (not null)
     * @return true if it's a valid action name, otherwise false
     */
    public static boolean isValidName(String name) {
        assert name != null;

        if (validNames.contains(name)) {
            return true;
        }
        String[] words = name.split("\\s+");
        if (words.length == 2
                && "signal".equals(words[0])
                && signals.exists(words[1])) {
            return true;
        }
        return hasValidPrefix(name);
    }

    /**
     * Validate an action string.
     *
     * @param string which action string to validate (not null)
     * @return true if it's a valid action string, otherwise false
     */
    public static boolean isValidString(String string) {
        if (validNames.contains(string)) {
            return true;
        }
        String[] words = string.split("\\s+");
        if (words.length == 3
                && "signal".equals(words[0])
                && signals.exists(words[1])) {
            int sourceIndex = Integer.valueOf(words[2]);
            return Hotkey.getInstance(sourceIndex) != null;
        }
        return hasValidPrefix(string);
    }
    // *************************************************************************
    // test cases

    /**
     * Test cases for this class.
     *
     * @param ignored
     */
    public static void main(String[] ignored) {
        System.out.print("Test results for class Actions:\n");
        // TODO
    }
}